﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Sherwood.Security;
using System.Text;
using log4net;

namespace Sherwood.SignOn.Server.Models.Services
{
    public class UserSessionHelper
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(UserSessionHelper));

        public static bool IsCurrentUserAdministrator()
        {
            IUserSession session = GetCurrentUserSession();
            if (session == null || session.UserAccount == null)
                return false;
            return UserAccountHelper.IsAdministrator(session.UserAccount.UserName);
        }

        public static string GetSignInTicket(IUserSession session, string clientSessionId, out string signature)
        {
            string timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
            string ssoSessionId = session.Id != null ? session.Id.ToString() : "";
            clientSessionId = clientSessionId != null ? clientSessionId : "";
            string ipAddress = session.IpAddress != null ? session.IpAddress : "";
            string userAccountId = session.UserAccount != null ? session.UserAccount.Id.ToString() : Guid.Empty.ToString();
            string ticket = "SI|" + ssoSessionId + "|" + clientSessionId + "|" + ipAddress + "|" + userAccountId + "|" + timestamp + "|" + IsPersistentSession;

            string privateKey = Config.Settings.RsaPrivateKey;

            Rsa cryptoProvider = Rsa.FromPrivateKey(privateKey);
            signature = cryptoProvider.SHA1Sign(ticket);

            return Convert.ToBase64String(Encoding.UTF8.GetBytes(ticket));
        }

        public static string GetSignOutTicket(IUserSession session, string clientSessionId, out string signature)
        {
            string timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
            string ssoSessionId = session.Id != null ? session.Id.ToString() : "";
            clientSessionId = clientSessionId != null ? clientSessionId : "";
            string ipAddress = session.IpAddress != null ? session.IpAddress : "";
            string userAccountId = session.UserAccount != null ? session.UserAccount.Id.ToString() : "";
            string ticket = "SO|" + ssoSessionId + "|" + clientSessionId + "|" + ipAddress + "|" + userAccountId + "|" + timestamp;

            string privateKey = Config.Settings.RsaPrivateKey;
            Rsa cryptoProvider = Rsa.FromPrivateKey(privateKey);
            signature = cryptoProvider.SHA1Sign(ticket);

            return Convert.ToBase64String(Encoding.UTF8.GetBytes(ticket));
        }

        /// <summary>
        /// Indicates whether or not the current session is persistent (active beyond a single browser session due to "remember me" function)
        /// Comment: A persistent session may be considered "unsafe" in certain instances and applications may want to request
        /// a new, non-persistent login.
        /// </summary>
        public static bool IsPersistentSession
        {
            get
            {
                return string.IsNullOrEmpty(Cookie.GetCookieValue("SignOnSession"));
            }
        }

        public static Guid CurrentUserSessionId
        {
            get
            {
                string sessionIdString = Cookie.GetCookieValue("SignOnSession");
                if (string.IsNullOrEmpty(sessionIdString))
                {
                    sessionIdString = Cookie.GetCookieValue("PersistentSignOnSession");
                }
                if (!string.IsNullOrEmpty(sessionIdString))
                {
                    try
                    {
                        Guid sessionId = new Guid(sessionIdString);
                        return sessionId;
                    }
                    catch { }
                }
                return Guid.Empty;
            }
            set
            {
                if (CurrentUserSessionId != Guid.Empty && !CurrentUserSessionId.Equals(value))
                {
                    EndCurrentUserSession();
                }
                Cookie.SetCookie("SignOnSession", value.ToString());

                IUserSession currentSession = GetCurrentUserSession();
                if (currentSession != null && currentSession.Persistent.GetValueOrDefault(false))
                {
                    Cookie.SetPersistentCookie("PersistentSignOnSession", value.ToString());
                }
            }
        }

        public static void EndCurrentUserSession()
        {
            Guid currentUserSessionId = CurrentUserSessionId;
            EndUserSession(currentUserSessionId);
            Cookie.RemoveCookie("SignOnSession");
            Cookie.RemoveCookie("PersistentSignOnSession");
        }

        public static IUserSession GetCurrentUserSession()
        {
            Guid currentUserSessionId = CurrentUserSessionId;
            if (currentUserSessionId != Guid.Empty)
            {
                return Data.UserSessions.GetUserSession(currentUserSessionId);
            }
            return null;
        }

        public static Guid StartUserSession(Guid userAccountId, string ipAddress, bool persistent)
        {
            return Data.UserSessions.AddUserSession(userAccountId, ipAddress, persistent).Id;

        }

        public static void EndUserSession(Guid userSessionId)
        {
            IUserSession session = Data.UserSessions.GetUserSession(userSessionId);

            if (session != null)
            {
                var signOutRequestResults = new List<IAsyncResult>();
                var signOutRequests = new List<System.Net.WebRequest>();

                //Call signout urls
                foreach (IUserSessionClientRecord clientRecord in session.ClientRecords)
                {
                    if (!string.IsNullOrEmpty(clientRecord.SignOutUrl))
                    {
                        string signOutUrl = clientRecord.SignOutUrl;
                        string signature;
                        string ticket = UserSessionHelper.GetSignOutTicket(session, clientRecord.ClientSessionId, out signature);
                        string urlTicket = HttpUtility.UrlEncode(ticket);
                        string urlSignature = HttpUtility.UrlEncode(signature);
                        signOutUrl = signOutUrl.Replace("{signoutticket}", urlTicket).Replace("{signoutsignature}", urlSignature);

                        System.Net.WebRequest signOutRequest = System.Net.HttpWebRequest.Create(signOutUrl);
                        signOutRequest.Timeout = Config.Settings.SignOutRequestTimeoutInMilliseconds; 
                        IAsyncResult signOutResult = signOutRequest.BeginGetResponse(null, null);
                        signOutRequestResults.Add(signOutResult);
                        signOutRequests.Add(signOutRequest);
                    }

                }

                //Wait until each request is complete
                int maxTimeout = Config.Settings.SignOutRequestTimeoutInMilliseconds;
                for (int i = 0; i < signOutRequestResults.Count; i++)
                {
                    IAsyncResult requestResult = signOutRequestResults[i];
                    System.Net.WebRequest request = signOutRequests[i];
                    try
                    {
                        DateTime startTime = DateTime.Now;
                        while (!requestResult.IsCompleted)
                        {
                            System.Threading.Thread.Sleep(10);
                            if ((DateTime.Now - startTime) > new TimeSpan(0, 0, 0, 0, maxTimeout*100))
                                throw new Exception("Timeout exceeded while waiting for response from signout URL");
                        }
                        request.EndGetResponse(requestResult);
                    }
                    catch (Exception ex)
                    {
                        //Timeout etc.
                        if (Log.IsWarnEnabled)
                            Log.Warn("Failed to call signout url " + request.RequestUri, ex);
                    }
                }

                Data.UserSessions.RemoveUserSession(userSessionId);

            }

        }
    }
}